package com.cooldatasoft.service;

import java.util.HashMap;
import java.util.Map;

import com.cooldatasoft.data.NumberWordEnum;
import com.cooldatasoft.exception.RangeException;

/**
 * Service class which converts integer to its text representation
 * 
 * @author Fatih Mehmet UCAR - fmucar@gmail.com
 *
 */
public class NumberToTextService {

	private static final String AND = "AND";
	private static final String SPACE = " ";
	
	/**
	 * Map that holds word representations of marked numbers 
	 */
	private final static Map<Integer, NumberWordEnum> map = new HashMap<Integer, NumberWordEnum>();
	static {
		for (NumberWordEnum current : NumberWordEnum.values()) {
			map.put(current.getValue(), current);
		}
	}

	/**
	 * Singleton instance of this service class
	 */
	private static NumberToTextService instance;
	
	/**
	 * Singleton service class. Hide constructor from outside world
	 */
	private NumberToTextService(){
	}
	
	/**
	 * Provides access to singleton instance of this class to outside world
	 * @return
	 */
	public static NumberToTextService getInstance(){
		if(null == instance){
			//lazy initialization of instance
			instance = new NumberToTextService();
		}
		return instance;
	}
	
	public synchronized String convertNumberToString(int number) {
		//Check if integer is a minus value
		boolean isNegative=false;
		if (0 > number) {
			isNegative = true;
			number = Math.abs(number);
		}
		
		if(number>=NumberWordEnum.BILLION.getValue()){
			//throw exception if the number is out of range
			throw new RangeException("Service will only handle numbers from -999,999,999 to 999,999,999!");
		}
		
		StringBuffer numberText = convertNumberToStringHelper(number);
		
		if(isNegative){
			numberText.insert(0,"MINUS ");
		}
		return numberText.toString();
	}
	/**
	 * Service method to convert integer to its english text representation
	 * Works recursively to covert the number into its text representation
	 * @param number integer number to be converted
	 * @return String String representation of the number passed
	 */
	private StringBuffer convertNumberToStringHelper(int number) {

		StringBuffer result = new StringBuffer();
		
		if (number >= NumberWordEnum.MILLION.getValue()) {
			process(number, result,NumberWordEnum.MILLION);
		} else if (number >= NumberWordEnum.THOUSAND.getValue()) {
			process(number, result,NumberWordEnum.THOUSAND);
		} else if (number >= NumberWordEnum.HUNDRED.getValue()) {
			process(number, result,NumberWordEnum.HUNDRED);
		} else if (number >= NumberWordEnum.TEN.getValue()) {
			if (number < NumberWordEnum.TWENTY.getValue()) {
				result.append(map.get(number));
			} else {
				int part = (int) (number / NumberWordEnum.TEN.getValue());
				part *= NumberWordEnum.TEN.getValue();
				result.append(map.get(part));
				if (number % NumberWordEnum.TEN.getValue() > 0) {
					//convert remaining part to text recursively
					result.append(SPACE).append(convertNumberToString(number % NumberWordEnum.TEN.getValue()));
				}
			}
		} else {
			//convert last digit to text
			result.append(map.get(number));
		}		
		return result;
	}
	/**
	 * Recursion helper method
	 * @param number
	 * @param result
	 * @param numberWordEnum
	 */
	private void process(int number, StringBuffer result,NumberWordEnum numberWordEnum) {
		int part = (int) (number / numberWordEnum.getValue());
		result.append(convertNumberToStringHelper(part)).append(SPACE);
		result.append(numberWordEnum);
		if (number % numberWordEnum.getValue() > 0) {
			result.append(SPACE);
			if(numberWordEnum == NumberWordEnum.HUNDRED){
				result.append(AND).append(SPACE);
			}
			result.append(convertNumberToStringHelper(number % numberWordEnum.getValue()));
		}
	}
}
